<template>
  <el-dialog :title="`📁 上传文件到「${currentParentName}」`" v-model="dialogVisible" :width="isMobile ? '100%' : '90%'"
    :top="isMobile ? '0' : '5vh'" :fullscreen="isMobile" :close-on-click-modal="false" :close-on-press-escape="false"
    class="upload-dialog" :class="{ 'light-theme': !isDarkTheme, 'dark-theme': isDarkTheme }" @closed="handleClose"
    @open="handleOpen" align-center>
    <div class="upload-container">
      <div class="sticky-nav-bar">
        <div class="nav-content">
          <span class="nav-title">文件列表</span>
          <div class="nav-actions">
            <el-button-group v-if="fileList.length > 0">
              <el-button size="small" @click="startAll" :disabled="allStarted" :icon="VideoPlay">全部开始</el-button>
              <el-button size="small" @click="cancelAll" :icon="VideoPause">全部暂停/取消</el-button>
            </el-button-group>
            <el-tag v-if="fileList.length > 0" size="small" type="info">{{ fileList.length }} 个文件</el-tag>
          </div>
        </div>
      </div>

      <div v-if="fileList.length === 0" class="upload-area" @dragover="handleDragOver" @drop="handleDrop">
        <div class="upload-icon">
          <el-icon :size="48" color="#409eff">
            <UploadFilled />
          </el-icon>
        </div>
        <h3>将文件拖到此处，或</h3>
        <el-button type="primary" size="default" @click="$refs.uploader?.$el?.querySelector('input')?.click()">
          选择文件
        </el-button>
        <p class="tip">支持多文件、拖拽上传 | 单文件最大 10GB</p>
      </div>

      <el-scrollbar v-else class="scrollable-file-list">
        <el-table :data="fileList" style="width: 100%" :show-header="false" :row-class-name="tableRowClassName"
          :class="{ 'mobile-table': isMobile, 'light-theme': !isDarkTheme, 'dark-theme': isDarkTheme }">
          <el-table-column>
            <template #default="scope">
              <div class="file-item"
                :class="{ 'mobile-file-item': isMobile, 'light-theme': !isDarkTheme, 'dark-theme': isDarkTheme }">
                <div class="file-info">
                  <el-icon :size="isMobile ? 18 : 20" class="file-icon" :class="getFileIconClass(scope.row.file.name)">
                    <component :is="getFileIcon(scope.row.file.name)" />
                  </el-icon>
                  <div class="text">
                    <div class="name">{{ scope.row.file.name }}</div>
                    <div class="meta">
                      {{ formatSize(scope.row.file.size) }}
                      <span v-if="scope.row.duration"> · {{ scope.row.duration }}</span>
                      <span v-if="scope.row.status === 'uploading' && scope.row.speed > 0">
                        · {{ scope.row.speed }} KB/s
                      </span>
                      <span
                        v-if="scope.row.status === 'uploading' && scope.row.progress > 0 && scope.row.progress < 100">
                        · {{ scope.row.progress }}%
                      </span>
                    </div>
                  </div>
                </div>

                <div class="file-status">
                  <el-tag :type="scope.row.statusTag" size="small" effect="plain" class="status-tag"
                    :class="{ 'mobile-status-tag': isMobile, 'light-theme': !isDarkTheme, 'dark-theme': isDarkTheme }">
                    {{ scope.row.statusText }}
                  </el-tag>
                  <div class="actions">
                    <el-button v-if="['pending', 'error'].includes(scope.row.status)" size="small" :icon="Upload"
                      @click="startUpload(scope.row)" circle class="action-btn start-btn"
                      :class="{ 'mobile-action-btn': isMobile }" />
                    <el-button v-if="scope.row.status === 'uploading'" size="small" :icon="VideoPause"
                      @click="cancelUpload(scope.row)" circle class="action-btn pause-btn"
                      :class="{ 'mobile-action-btn': isMobile }" />
                    <el-button v-if="scope.row.status === 'success' || scope.row.status === 'instant'" size="small"
                      :icon="Check" type="success" circle class="action-btn success-btn"
                      :class="{ 'mobile-action-btn': isMobile }" />
                  </div>
                </div>
              </div>
              <el-progress v-if="scope.row.progress > 0 && scope.row.progress < 100 && scope.row.status !== 'paused'"
                :percentage="scope.row.progress" :stroke-width="isMobile ? 4 : 6" :show-text="false"
                class="upload-progress" striped striped-flow />
            </template>
          </el-table-column>
        </el-table>
      </el-scrollbar>
    </div>

    <template #footer v-if="fileList.length > 0">
      <span class="dialog-footer">
        <el-button @click="handleClose" round>关闭</el-button>
      </span>
    </template>

    <el-upload ref="uploader" action="#" :auto-upload="false" :multiple="true" :on-change="handleFileChange"
      :show-file-list="false" style="display: none" />
  </el-dialog>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted, watch } from "vue";
import { ElMessage } from "element-plus";
import request from "@/utils/request";

import {
  UploadFilled,
  VideoPlay,
  VideoPause,
  Check,
  Document,
  Tickets,
  Present,
  Folder,
  Picture,
  VideoCamera,
  Headset,
  Cpu,
  Edit,
  Upload,
} from "@element-plus/icons-vue";

// --- 配置常量 ---
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB
const SMALL_FILE_SIZE = 10 * 1024 * 1024; // 10MB
const MAX_CONCURRENT_CHUNKS = 3;
const MAX_RETRY = 3;
const RETRY_DELAY_BASE_MS = 500; // 基础重试延迟 (ms)
const SPEED_UPDATE_INTERVAL_MS = 500; // 速度更新间隔 (ms)
const PROGRESS_UPDATE_INTERVAL_MS = 100; // 进度条更新间隔 (ms)，避免UI卡顿
const MAX_CONCURRENT_REQUESTS = 6; // 限制并发请求数，防止浏览器或服务器过载
const requestQueue = []; // 请求队列
let activeRequests = 0; // 当前活跃请求数

// --- 工具函数 ---
// 队列化请求，限制并发
const queuedRequest = (config) => {
  return new Promise((resolve, reject) => {
    const task = { config, resolve, reject };
    requestQueue.push(task);
    processQueue();
  });
};

const processQueue = () => {
  if (activeRequests >= MAX_CONCURRENT_REQUESTS || requestQueue.length === 0) {
    return;
  }
  const task = requestQueue.shift();
  activeRequests++;
  request(task.config)
    .then((res) => {
      task.resolve(res);
    })
    .catch((err) => {
      console.error(`[Upload] Request failed for ${task.config.url || 'unknown URL'}:`, err);
      task.reject(err);
    })
    .finally(() => {
      activeRequests--;
      processQueue();
    });
};

// 计算并格式化持续时间
const calculateAndFormatDuration = (startTime, endTime) => {
  if (!startTime || !endTime || endTime < startTime) {
    return '';
  }
  const durationMs = endTime - startTime;
  const seconds = Math.floor(durationMs / 1000);
  const minutes = Math.floor(seconds / 60);
  const hours = Math.floor(minutes / 60);

  if (hours > 0) {
    return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
  } else if (minutes > 0) {
    return `${minutes}m ${seconds % 60}s`;
  } else {
    return `${seconds}s`;
  }
};

// --- Props & Emits ---
const props = defineProps({
  visible: {
    type: Boolean,
    default: false,
  },
  currentParent: {
    type: String,
    default: "root",
  },
  currentParentName: {
    type: String,
    default: "根目录",
  },
});

const emit = defineEmits(["update:visible", "upload-success"]);

// --- Reactive State ---
const dialogVisible = ref(props.visible);
const fileList = ref([]);
const isMobile = ref(false);
const uploader = ref(null);
// 添加深色主题状态
const isDarkTheme = ref(false);

// --- Watchers ---
watch(
  () => props.visible,
  (newVal) => {
    dialogVisible.value = newVal;
  }
);

const allStarted = computed(() => {
  return (
    fileList.value.length > 0 &&
    fileList.value.every((f) => f.status !== "pending")
  );
});

// --- Lifecycle Hooks ---
onMounted(() => {
  checkScreenSize();
  window.addEventListener("resize", checkScreenSize);

  // 检测系统主题偏好
  checkSystemTheme();
  window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', checkSystemTheme);
});

onUnmounted(() => {
  window.removeEventListener("resize", checkScreenSize);
  window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', checkSystemTheme);
  // 确保在组件卸载时清理所有上传
  fileList.value.forEach((fileObj) => cancelUpload(fileObj));
});

// 检测系统主题偏好
const checkSystemTheme = () => {
  if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
    isDarkTheme.value = true;
  } else {
    isDarkTheme.value = false;
  }
};

// --- Methods ---
const checkScreenSize = () => {
  isMobile.value = window.innerWidth <= 768;
};

// 对话框打开时清理文件列表
const handleOpen = () => {
  // 清理之前的文件列表
  fileList.value.forEach((fileObj) => cancelUpload(fileObj));
  fileList.value = [];
};

// 文件选择处理
const handleFileChange = (file, fileListRaw) => {
  // 防止重复添加同一文件
  if (fileList.value.some((f) => f.uid === file.uid)) return;

  const fileObj = {
    uid: file.uid,
    file: file.raw,
    name: file.name,
    size: file.raw.size,
    status: "pending", // 'pending', 'uploading', 'paused', 'success', 'error', 'instant'
    statusText: "等待上传",
    statusTag: "info",
    progress: 0,
    speed: 0,
    lastLoaded: 0,
    lastTime: Date.now(),
    chunksUploaded: [],
    chunkStatusMap: {},
    fileId: null,
    totalChunks: 0,
    uploadedChunks: 0,
    abortController: null,
    // 新增字段用于高可用性
    lastProgressUpdate: 0, // 用于节流进度更新
    lastSpeedUpdate: 0,    // 用于节流速度更新
    retryCounts: {},       // 记录每个分片的重试次数
    isCompleting: false,   // 防止重复调用 completeUpload
    // 耗时统计相关
    startTime: null,
    endTime: null,
    duration: '', // 格式化后的耗时字符串
  };

  fileList.value.push(fileObj);
};

// 拖拽处理
const handleDragOver = (e) => {
  e.preventDefault();
};
const handleDrop = (e) => {
  e.preventDefault();
  const files = Array.from(e.dataTransfer.files);
  files.forEach((file) => {
    const fakeFile = { uid: Date.now() + Math.random(), raw: file, name: file.name };
    handleFileChange(fakeFile, []);
  });
};

// 关闭对话框处理
const handleClose = () => {
  // 取消所有上传
  fileList.value.forEach((fileObj) => cancelUpload(fileObj));
  fileList.value = [];
  emit("update:visible", false);
};

// 生成文件ID
const generateFileId = () => {
  return "upload_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
};

// 获取文件图标
const getFileIcon = (filename) => {
  const ext = filename.split(".").pop()?.toLowerCase();
  const icons = {
    mp4: VideoCamera,
    avi: VideoCamera,
    mov: VideoCamera,
    mkv: VideoCamera,
    pdf: Document,
    doc: Document,
    docx: Document,
    xlsx: Tickets,
    xls: Tickets,
    ppt: Present,
    pptx: Present,
    zip: Folder,
    rar: Folder,
    "7z": Folder,
    jpg: Picture,
    jpeg: Picture,
    png: Picture,
    gif: Picture,
    webp: Picture,
    mp3: Headset,
    wav: Headset,
    flac: Headset,
    js: Cpu,
    ts: Cpu,
    jsx: Cpu,
    tsx: Cpu,
    vue: Edit,
    html: Edit,
    css: Edit,
  };
  return icons[ext] || Document;
};

// 获取文件图标类名
const getFileIconClass = (filename) => {
  const ext = filename.split(".").pop()?.toLowerCase();
  const iconClasses = {
    pdf: "icon-pdf",
    doc: "icon-doc",
    docx: "icon-doc",
    xlsx: "icon-xls",
    xls: "icon-xls",
    ppt: "icon-ppt",
    pptx: "icon-ppt",
    zip: "icon-archive",
    rar: "icon-archive",
    "7z": "icon-archive",
    jpg: "icon-image",
    jpeg: "icon-image",
    png: "icon-image",
    gif: "icon-image",
    webp: "icon-image",
    mp4: "icon-video",
    avi: "icon-video",
    mov: "icon-video",
    mkv: "icon-video",
    mp3: "icon-audio",
    wav: "icon-audio",
    flac: "icon-audio",
    js: "icon-code",
    ts: "icon-code",
    jsx: "icon-code",
    tsx: "icon-code",
    vue: "icon-code",
    html: "icon-code",
    css: "icon-code",
  };
  return iconClasses[ext] || "icon-default";
};

// 格式化文件大小
const formatSize = (bytes) => {
  if (bytes === 0) return "0 B";
  const k = 1024;
  const sizes = ["B", "KB", "MB", "GB"];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
};

// 表格行类名
const tableRowClassName = ({ row }) => {
  return row.status === "error" ? "error-row" : "";
};

// --- 核心上传逻辑 ---

// 开始上传单个文件
const startUpload = async (fileObj) => {
  // 防止重复启动
  if (
    ["uploading", "success", "instant", "paused"].includes(fileObj.status)
  ) {
    console.log(
      `File ${fileObj.name} is already in progress or completed. Skipping start.`
    );
    return;
  }

  try {
    // 初始化状态
    fileObj.status = "uploading";
    fileObj.statusText = "准备上传...";
    fileObj.statusTag = "primary";
    fileObj.speed = 0;
    fileObj.lastLoaded = 0;
    fileObj.lastTime = Date.now();
    fileObj.lastProgressUpdate = 0;
    fileObj.lastSpeedUpdate = 0;
    fileObj.chunkStatusMap = {};
    fileObj.uploadedChunks = 0;
    fileObj.isCompleting = false;
    fileObj.retryCounts = {};
    fileObj.abortController = null;
    // 重置耗时相关字段
    fileObj.startTime = Date.now();
    fileObj.endTime = null;
    fileObj.duration = '';

    // 判断是否为小文件
    if (fileObj.size <= SMALL_FILE_SIZE) {
      await uploadSmallFile(fileObj);
      return;
    }

    // 大文件分片上传
    fileObj.fileId = generateFileId();
    fileObj.totalChunks = Math.ceil(fileObj.size / CHUNK_SIZE);

    // 初始化分片状态
    for (let i = 0; i < fileObj.totalChunks; i++) {
      fileObj.chunkStatusMap[i] = {
        status: "waiting",
        controller: null,
        retryCount: 0,
      };
    }

    // 开始上传分片
    uploadNextChunks(fileObj);
  } catch (error) {
    console.error(`Start upload error for ${fileObj.name}:`, error);
    handleError(fileObj, "启动失败", "启动上传失败");
  }
};

// 上传下一个可用的分片
const uploadNextChunks = (fileObj) => {
  if (fileObj.status !== "uploading") {
    return;
  }

  let uploadingCount = 0;
  let waitingIndices = [];

  for (const [indexStr, chunkInfo] of Object.entries(fileObj.chunkStatusMap)) {
    const index = parseInt(indexStr);
    if (chunkInfo.status === "uploading") {
      uploadingCount++;
    } else if (chunkInfo.status === "waiting") {
      waitingIndices.push(index);
    }
  }

  const slotsAvailable = MAX_CONCURRENT_CHUNKS - uploadingCount;

  // 检查是否所有分片都已成功上传
  if (uploadingCount === 0 && fileObj.uploadedChunks === fileObj.totalChunks) {
    completeUpload(fileObj);
    return;
  }

  if (slotsAvailable <= 0 || waitingIndices.length === 0) {
    return;
  }

  const chunksToStart = Math.min(slotsAvailable, waitingIndices.length);
  const promises = [];

  for (let i = 0; i < chunksToStart; i++) {
    const index = waitingIndices[i];
    // 再次检查状态，防止并发问题
    if (fileObj.chunkStatusMap[index]?.status === "waiting") {
      promises.push(uploadSingleChunk(fileObj, index));
    }
  }

  // 递归调用自身，继续上传
  Promise.allSettled(promises).then(() => {
    uploadNextChunks(fileObj);
  });
};

// 上传单个分片
const uploadSingleChunk = async (fileObj, index) => {
  const chunkInfo = fileObj.chunkStatusMap[index];

  // 防止重复上传
  if (chunkInfo.status !== "waiting") {
    console.log(
      `Chunk ${index} of ${fileObj.name} is not waiting (status: ${chunkInfo.status}). Skipping upload.`
    );
    return;
  }

  chunkInfo.status = "uploading";
  fileObj.statusText = `上传中 (${fileObj.uploadedChunks + 1}/${fileObj.totalChunks})`;

  const start = index * CHUNK_SIZE;
  const end = Math.min(fileObj.size, start + CHUNK_SIZE);
  const chunk = fileObj.file.slice(start, end);

  const formData = new FormData();
  formData.append("chunk", chunk);
  formData.append("fileId", fileObj.fileId);
  formData.append("chunkIndex", index);
  formData.append("totalChunks", fileObj.totalChunks);

  chunkInfo.controller = new AbortController();

  let retryCount = 0;
  while (retryCount <= MAX_RETRY) {
    try {
      // 使用队列化请求
      await queuedRequest({
        url: "/upload/chunk",
        method: "post",
        data: formData,
        headers: { "Content-Type": "multipart/form-data" },
        signal: chunkInfo.controller.signal,
        onUploadProgress: (e) => {
          // 确保上传仍在进行中
          if (fileObj.status !== "uploading") return;

          const now = Date.now();
          // 节流速度更新
          if (now - fileObj.lastSpeedUpdate > SPEED_UPDATE_INTERVAL_MS) {
            const timeDiff = (now - fileObj.lastTime) / 1000;
            if (timeDiff > 0) { // 避免除以0
              const bytesDiff = e.loaded - fileObj.lastLoaded;
              fileObj.speed = Math.floor((bytesDiff / timeDiff) / 1024);
            }
            fileObj.lastLoaded = e.loaded;
            fileObj.lastTime = now;
            fileObj.lastSpeedUpdate = now;
          }

          // 节流进度更新，避免UI卡顿
          if (now - fileObj.lastProgressUpdate > PROGRESS_UPDATE_INTERVAL_MS) {
            const chunkProgress = Math.floor((e.loaded / e.total) * 100);
            const overallProgress = Math.floor(
              ((fileObj.uploadedChunks + chunkProgress / 100) / fileObj.totalChunks) *
              100
            );
            // 确保进度不会超过99%，直到完成
            fileObj.progress = Math.min(99, overallProgress);
            fileObj.lastProgressUpdate = now;
          }
        },
      });

      // 上传成功
      chunkInfo.status = "success";
      fileObj.uploadedChunks++;
      fileObj.statusText = `上传中 (${fileObj.uploadedChunks}/${fileObj.totalChunks})`;
      // 继续上传下一个分片
      uploadNextChunks(fileObj);
      return; // 成功后退出循环
    } catch (error) {
      // 处理取消或中止
      if (error.code === "ERR_CANCELED" || error.name === "AbortError") {
        chunkInfo.status = "cancelled";
        console.log(`Chunk ${index} of ${fileObj.name} was cancelled.`);
        return;
      }
      // 处理重试
      retryCount++;
      chunkInfo.retryCount = retryCount;
      console.warn(
        `Chunk ${index} of ${fileObj.name} failed, retry ${retryCount}/${MAX_RETRY}:`,
        error.message
      );

      if (retryCount > MAX_RETRY) {
        chunkInfo.status = "error";
        // 确保在达到最大重试次数后，如果文件仍在上传状态，则标记为错误
        if (fileObj.status === "uploading") {
          handleError(fileObj, "分片上传失败", "分片上传失败，请重试");
        }
        return; // 达到最大重试次数后退出
      }
      // 指数退避延迟重试
      const delay = Math.pow(2, retryCount) * RETRY_DELAY_BASE_MS;
      console.log(`Retrying chunk ${index} in ${delay}ms...`);
      await new Promise((r) => setTimeout(r, delay));
    }
  }
};

// 完成分片上传
const completeUpload = async (fileObj) => {
  // 防止重复调用
  if (fileObj.isCompleting || fileObj.status !== "uploading") {
    console.log(`File ${fileObj.name} completion already in progress or not uploading.`);
    return;
  }
  fileObj.isCompleting = true;
  fileObj.statusText = "合并中...";
  try {
    // 使用队列化请求
    // 确保 props.currentParent 有默认值，防止 undefined
    const parentToUse = props.currentParent || 'root';
    await queuedRequest({
      url: "/upload/complete",
      method: "post",
      data: {
        fileId: fileObj.fileId,
        fileName: fileObj.name,
        fileSize: fileObj.size,
        parent: parentToUse,
      },
    });
    // 成功
    fileObj.status = "success";
    fileObj.statusText = "上传成功";
    fileObj.statusTag = "success";
    fileObj.progress = 100;
    fileObj.speed = 0;
    // 记录结束时间并计算耗时
    fileObj.endTime = Date.now();
    fileObj.duration = calculateAndFormatDuration(fileObj.startTime, fileObj.endTime);
    ElMessage.success(`${fileObj.name} 上传成功 (耗时: ${fileObj.duration})`);
    emit("upload-success");
  } catch (error) {
    console.error(`Complete upload error for ${fileObj.name}:`, error);
    // 记录结束时间
    fileObj.endTime = Date.now();
    fileObj.duration = calculateAndFormatDuration(fileObj.startTime, fileObj.endTime);
    if (fileObj.status === "uploading") {
      handleError(fileObj, "合并失败", `合并失败：${error.message || '未知错误'}`);
    }
  } finally {
    fileObj.isCompleting = false; // 重置标志
  }
};

// 上传小文件
const uploadSmallFile = async (fileObj) => {
  const formData = new FormData();
  formData.append("file", fileObj.file);
  formData.append("fileName", fileObj.name);
  // 确保 props.currentParent 有默认值，防止 undefined
  const parentToUse = props.currentParent || 'root';
  formData.append("parent", parentToUse);

  fileObj.abortController = new AbortController();

  try {
    // 使用队列化请求
    await queuedRequest({
      url: "/files",
      method: "post",
      data: formData,
      signal: fileObj.abortController.signal,
      onUploadProgress: (e) => {
        if (fileObj.status !== "uploading") return;

        const now = Date.now();
        // 节流速度更新
        if (now - fileObj.lastSpeedUpdate > SPEED_UPDATE_INTERVAL_MS) {
          const timeDiff = (now - fileObj.lastTime) / 1000;
          if (timeDiff > 0) { // 避免除以0
            const bytesDiff = e.loaded - fileObj.lastLoaded;
            fileObj.speed = Math.floor((bytesDiff / timeDiff) / 1024);
          }
          fileObj.lastLoaded = e.loaded;
          fileObj.lastTime = now;
          fileObj.lastSpeedUpdate = now;
        }
        // 节流进度更新
        if (now - fileObj.lastProgressUpdate > PROGRESS_UPDATE_INTERVAL_MS) {
          fileObj.progress = Math.floor((e.loaded / e.total) * 100);
          fileObj.lastProgressUpdate = now;
        }
      },
    });

    // 小文件上传成功
    fileObj.status = "success";
    fileObj.statusText = "上传成功";
    fileObj.statusTag = "success";
    fileObj.progress = 100;
    fileObj.speed = 0;
    // 记录结束时间并计算耗时
    fileObj.endTime = Date.now();
    fileObj.duration = calculateAndFormatDuration(fileObj.startTime, fileObj.endTime);
    ElMessage.success(`${fileObj.name} 上传成功 (耗时: ${fileObj.duration})`);
    emit("upload-success");
  } catch (error) {
    // 记录结束时间
    fileObj.endTime = Date.now();
    fileObj.duration = calculateAndFormatDuration(fileObj.startTime, fileObj.endTime);
    // 处理取消或中止
    if (error.code === "ERR_CANCELED" || error.name === "AbortError") {
      fileObj.status = "paused";
      fileObj.statusText = "已暂停";
      fileObj.statusTag = "info";
    } else {
      // 处理其他错误
      if (fileObj.status === "uploading") {
        handleError(fileObj, "上传失败", `上传失败：${error.message || '未知错误'}`);
      }
    }
  }
};

// 取消/暂停单个文件上传
const cancelUpload = (fileObj) => {
  console.log(`Cancelling upload for ${fileObj.name}`);
  // 记录结束时间并计算耗时
  if (fileObj.startTime && !fileObj.endTime) {
    fileObj.endTime = Date.now();
    fileObj.duration = calculateAndFormatDuration(fileObj.startTime, fileObj.endTime);
  }
  // 取消主文件控制器
  if (fileObj.abortController) {
    fileObj.abortController.abort();
    fileObj.abortController = null;
  }

  // 取消所有分片控制器
  if (fileObj.chunkStatusMap) {
    Object.values(fileObj.chunkStatusMap).forEach((chunkInfo) => {
      if (chunkInfo.controller) {
        chunkInfo.controller.abort();
        chunkInfo.controller = null;
      }
      // 更新分片状态
      if (chunkInfo.status === "uploading" || chunkInfo.status === "waiting") {
        chunkInfo.status = "cancelled";
      }
    });
  }

  // 更新文件状态
  if (fileObj.status === "uploading" || fileObj.status === "pending") {
    fileObj.status = "paused";
    fileObj.statusText = "已暂停";
    fileObj.statusTag = "info";
    fileObj.speed = 0;
  }
};

// 开始所有文件上传
const startAll = () => {
  fileList.value.forEach((fileObj) => {
    if (fileObj.status === "pending" || fileObj.status === "error") {
      startUpload(fileObj);
    }
  });
};

// 取消/暂停所有文件上传
const cancelAll = () => {
  fileList.value.forEach((fileObj) => {
    cancelUpload(fileObj);
  });
};

// 统一错误处理
const handleError = (fileObj, statusText, message) => {
  // 记录结束时间并计算耗时
  if (fileObj.startTime && !fileObj.endTime) {
    fileObj.endTime = Date.now();
    fileObj.duration = calculateAndFormatDuration(fileObj.startTime, fileObj.endTime);
  }
  fileObj.status = "error";
  fileObj.statusText = statusText;
  fileObj.statusTag = "danger";
  fileObj.speed = 0;
  fileObj.progress = 0; // 重置进度
  ElMessage.error(`${fileObj.name} ${message} (耗时: ${fileObj.duration})`);
};
</script>

<style scoped>
/* 浅色主题变量 */
.light-theme {
  --bg-color-overlay: #ffffff;
  --fill-color-light: #f5f7fa;
  --border-color-lighter: #dcdfe6;
  --text-color-primary: #303133;
  --text-color-secondary: #909399;
  --color-primary: #409eff;
  --color-primary-light-9: #ecf5ff;
  --color-danger: #f56c6c;
  --color-danger-light-9: #fef0f0;
  --color-success: #67c23a;
  --color-warning: #e6a23c;
  --color-info: #909399;
  --box-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  --bg-color: #f4f4f5;
}

/* 深色主题变量 */
.dark-theme {
  --bg-color-overlay: #1d1e1f;
  --fill-color-light: #2d2e30;
  --border-color-lighter: #4a4b4d;
  --text-color-primary: #e4e7ed;
  --text-color-secondary: #a0a0a2;
  --color-primary: #409eff;
  --color-primary-light-9: #1a2638;
  --color-danger: #f56c6c;
  --color-danger-light-9: #3a1010;
  --color-success: #67c23a;
  --color-warning: #e6a23c;
  --color-info: #909399;
  --box-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.3);
  --bg-color: #141414;
}

.upload-dialog :deep(.el-dialog) {
  border-radius: 0;
  box-shadow: var(--el-box-shadow-light);
  overflow: hidden;
  margin: 0 auto !important;
  border: 1px solid var(--el-border-color-lighter);
}

.upload-dialog :deep(.el-dialog__header) {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 20px 30px;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

.upload-dialog :deep(.el-dialog__title) {
  color: white;
  font-weight: 600;
  font-size: 18px;
}

.upload-dialog :deep(.el-dialog__body) {
  padding: 15px;
  background-color: var(--bg-color-overlay);
}

.upload-container {
  height: 70vh;
  display: flex;
  flex-direction: column;
  background-color: var(--bg-color-overlay);
}

.sticky-nav-bar {
  position: sticky;
  top: 0;
  z-index: 10;
  background-color: var(--el-bg-color-overlay);
  border-bottom: 1px solid var(--el-border-color-light);
  padding: 12px 15px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  flex-shrink: 0;
}

.nav-content {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}

.nav-title {
  font-weight: bold;
  font-size: 16px;
  color: var(--el-text-color-primary);
}

.nav-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.upload-area {
  text-align: center;
  padding: 60px 20px;
  border: 2px dashed var(--el-border-color-lighter);
  border-radius: 0;
  background: var(--el-fill-color-light);
  transition: all 0.3s ease;
  flex-grow: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.upload-area:hover {
  border-color: var(--el-color-primary);
  background: var(--el-color-primary-light-9);
}

.upload-area .upload-icon {
  margin-bottom: 15px;
}

.upload-area h3 {
  margin: 0 0 12px 0;
  color: var(--el-text-color-primary);
  font-weight: 500;
}

.upload-area .tip {
  margin: 12px 0 0 0;
  color: var(--el-text-color-secondary);
  font-size: 13px;
}

.scrollable-file-list {
  flex-grow: 1;
  overflow: hidden;
  border: 1px solid var(--el-border-color-lighter);
  border-radius: 0;
  background-color: var(--el-bg-color-overlay);
}

.file-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px 20px;
  border-bottom: 1px solid var(--el-border-color-lighter);
  transition: background-color 0.2s ease;
  background-color: var(--el-bg-color-overlay);
}

.file-item:hover {
  background-color: var(--el-fill-color-light);
}

.file-info {
  display: flex;
  align-items: center;
  flex: 1;
  min-width: 0;
}

.file-icon {
  margin-right: 16px;
  flex-shrink: 0;
  color: var(--el-text-color-secondary);
}

.file-icon.icon-pdf {
  color: #ff4545;
}

.file-icon.icon-doc {
  color: #2196f3;
}

.file-icon.icon-xls {
  color: #4caf50;
}

.file-icon.icon-ppt {
  color: #ff9800;
}

.file-icon.icon-archive {
  color: #9e9e9e;
}

.file-icon.icon-image {
  color: #e91e63;
}

.file-icon.icon-video {
  color: #673ab7;
}

.file-icon.icon-audio {
  color: #00bcd4;
}

.file-icon.icon-code {
  color: #ff5722;
}

.file-icon.icon-default {
  color: var(--el-text-color-secondary);
}

.text {
  flex: 1;
  min-width: 0;
}

.name {
  font-size: 15px;
  font-weight: 500;
  color: var(--el-text-color-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-bottom: 4px;
}

.meta {
  font-size: 13px;
  color: var(--el-text-color-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.file-status {
  display: flex;
  align-items: center;
  gap: 15px;
  flex-shrink: 0;
  margin-left: 15px;
}

.status-tag {
  min-width: 80px;
  text-align: center;
  border-radius: 0;
  padding: 0 10px;
  height: 26px;
  line-height: 24px;
  font-size: 12px;
  border-width: 1px;
  border-style: solid;
  background-color: transparent;
}

.status-tag.el-tag--info {
  color: var(--el-color-info);
  border-color: var(--el-color-info);
}

.status-tag.el-tag--primary {
  color: var(--el-color-primary);
  border-color: var(--el-color-primary);
}

.status-tag.el-tag--success {
  color: var(--el-color-success);
  border-color: var(--el-color-success);
}

.status-tag.el-tag--danger {
  color: var(--el-color-danger);
  border-color: var(--el-color-danger);
}

.status-tag.el-tag--warning {
  color: var(--el-color-warning);
  border-color: var(--el-color-warning);
}

.actions {
  display: flex;
  gap: 8px;
}

.action-btn {
  padding: 9px;
  min-width: auto;
  border: none;
  background-color: transparent;
  color: var(--el-text-color-secondary);
  transition: all 0.2s ease;
}

.action-btn:hover {
  background-color: var(--el-fill-color);
  color: var(--el-text-color-primary);
}

.action-btn:focus-visible {
  outline: 2px solid var(--el-color-primary);
  outline-offset: 1px;
}

.start-btn:hover {
  color: var(--el-color-primary);
}

.pause-btn:hover {
  color: var(--el-color-warning);
}

.success-btn:hover {
  color: var(--el-color-success);
}

.upload-progress {
  margin: 12px 0 0 0;
  border-radius: 0;
}

:deep(.error-row) {
  background-color: var(--el-color-danger-light-9) !important;
}

:deep(.error-row .file-item) {
  background-color: transparent;
}

:deep(.error-row .file-item:hover) {
  background-color: var(--el-color-danger-light-8) !important;
}

.dialog-footer {
  text-align: center;
  padding: 20px 0 15px 0;
}

/* 移动端优化样式 */
@media (max-width: 768px) {
  .sticky-nav-bar {
    padding: 10px 12px;
  }

  .nav-content {
    flex-direction: column;
    align-items: stretch;
  }

  .nav-title {
    font-size: 15px;
    text-align: center;
  }

  .nav-actions {
    justify-content: center;
    flex-wrap: wrap;
  }

  .nav-actions .el-button-group {
    width: 100%;
    display: flex;
    margin-bottom: 8px;
  }

  .nav-actions .el-button-group .el-button {
    flex: 1;
    font-size: 13px;
    padding: 8px 10px;
  }

  .nav-actions .el-tag {
    align-self: center;
    font-size: 12px;
    padding: 0 8px;
    height: 24px;
    line-height: 22px;
  }

  .upload-container {
    height: 80vh;
  }

  .file-item {
    padding: 14px 15px;
    flex-wrap: wrap;
  }

  .file-info {
    width: 100%;
    margin-bottom: 10px;
  }

  .file-status {
    width: 100%;
    justify-content: space-between;
    margin-left: 0;
  }

  .name {
    font-size: 14px;
  }

  .meta {
    font-size: 12px;
  }

  .status-tag {
    min-width: 70px;
    font-size: 11px;
    height: 24px;
    line-height: 22px;
  }

  .actions {
    gap: 6px;
  }

  .action-btn {
    padding: 8px;
  }

  /* 新增或修改的移动端优化样式 */
  .upload-dialog :deep(.el-dialog__header) {
    padding: 15px 20px;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
  }

  .upload-dialog :deep(.el-dialog__body) {
    padding: 10px;
  }

  .upload-dialog :deep(.el-dialog__footer) {
    padding: 10px;
  }

  .upload-area {
    padding: 40px 15px;
  }

  .upload-area h3 {
    font-size: 16px;
    margin-bottom: 10px;
  }

  .upload-area .tip {
    font-size: 12px;
  }

  .file-icon {
    margin-right: 12px;
  }

  .text {
    min-width: 0;
  }

  .name {
    font-size: 15px;
    margin-bottom: 3px;
  }

  .meta {
    font-size: 12px;
  }

  .file-status {
    gap: 10px;
  }

  .status-tag {
    min-width: 65px;
    font-size: 11px;
    padding: 0 8px;
    height: 22px;
    line-height: 20px;
  }

  .actions {
    gap: 6px;
  }

  .action-btn {
    padding: 7px;
  }

  .upload-progress {
    margin-top: 10px;
  }

  .dialog-footer {
    padding: 15px 0 10px 0;
  }

  .scrollable-file-list {
    border: none;
    border-radius: 0;
  }

  .file-item {
    padding: 12px 15px;
    border-bottom: 1px solid var(--el-border-color-lighter);
  }

  .file-icon {
    font-size: 18px;
  }

  .name {
    font-size: 14px;
  }

  .meta {
    font-size: 11px;
  }

  /* 移动端表格优化 */
  :deep(.mobile-table .el-table__row) {
    display: block;
    margin-bottom: 10px;
    border-radius: 0;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
    border: 1px solid var(--el-border-color-lighter);
  }

  :deep(.mobile-table .el-table__cell) {
    display: block;
    width: 100% !important;
  }

  :deep(.mobile-table .el-table__body) {
    display: block;
  }

  .mobile-file-item {
    flex-direction: column;
    align-items: flex-start;
    padding: 12px;
  }

  .mobile-file-item .file-info {
    width: 100%;
    margin-bottom: 12px;
  }

  .mobile-file-item .file-status {
    width: 100%;
    justify-content: space-between;
  }

  .mobile-status-tag {
    min-width: 60px;
    font-size: 10px;
    height: 20px;
    line-height: 18px;
    padding: 0 6px;
  }

  .mobile-action-btn {
    padding: 6px;
    font-size: 14px;
  }
}
</style>